package org.dicr.util.jds.conn;

import java.util.*;
import java.util.prefs.*;

import org.dicr.event.*;

/***********************************************************************************************************************
 * @author Igor A Tarasov me@dicr.dn.ua
 * @version 030113
 **********************************************************************************************************************/
public class ConnectionsFactory implements ProgramEventGenerator {
	Preferences connRoot = Preferences.userNodeForPackage(this.getClass());

	/** Connection added event */
	public static final int EVENT_CONNECTION_ADDED = 1;

	/** Connection removed event */
	public static final int EVENT_CONNECTION_REMOVED = 2;

	/** Connection changed event */
	public static final int EVENT_CONNECTION_CHANGED = 3;

	/** Change default connection event */
	public static final int EVENT_DEFAULT_CHANGED = 4;

	ProgramEventMulticaster listeners = new ProgramEventMulticaster();

	private PreferencesListener preferencesListener = new PreferencesListener();

	/*******************************************************************************************************************
	 * Конструктор
	 ******************************************************************************************************************/
	public ConnectionsFactory() {
		try {
			addPreferencesListeners(this.connRoot);
		} catch (Exception ex) {
			DicrExceptionHandler.handleException(ex, this);
		}
	}

	/*******************************************************************************************************************
	 * Регистрируем слушателя изменений настроек
	 ******************************************************************************************************************/
	void addPreferencesListeners(Preferences root) throws BackingStoreException {
		try {
			root.addNodeChangeListener(this.preferencesListener);
			root.addPreferenceChangeListener(this.preferencesListener);
			String[] names = root.childrenNames();
			for (int i = 0; i < names.length; i++)
				addPreferencesListeners(root.node(names[i]));
		} catch (IllegalStateException ex) {
			// NOP
		}
	}

	/*******************************************************************************************************************
	 * Разрегистрируем слушателя изменения настроек
	 ******************************************************************************************************************/
	void removePreferencesListener(Preferences root) throws BackingStoreException {
		try {
			root.removeNodeChangeListener(this.preferencesListener);
			root.removePreferenceChangeListener(this.preferencesListener);
			String[] names = root.childrenNames();
			for (int i = 0; i < names.length; i++)
				removePreferencesListener(root.node(names[i]));
		} catch (IllegalStateException ex) {
			// NOP
		}
	}

	/*******************************************************************************************************************
	 * Устанавливает параметр DefaultConnection выбирая из первых
	 ******************************************************************************************************************/
	private synchronized long checkDefaultConnection() {
		long defid = -1;
		try {
			String defname = this.connRoot.get("Default", null);
			if (defname != null && this.connRoot.nodeExists(defname)) defid = Long.parseLong(defname);
			else {
				String[] names = this.connRoot.childrenNames();
				if (names.length > 0) {
					defname = names[0];
					this.connRoot.put("Default", defname);
					defid = Long.parseLong(defname);
				} else this.connRoot.remove("Default");
			}
		} catch (BackingStoreException ex) {
			DicrExceptionHandler.handleException(ex, this);
		}
		return defid;
	}

	/*******************************************************************************************************************
	 * Возвращает список ID соединений
	 * 
	 * @return .
	 * @throws BackingStoreException
	 ******************************************************************************************************************/
	public synchronized Vector<Long> getConnectionsID() throws BackingStoreException {
		Vector<Long> ids = new Vector<Long>();
		String[] names = this.connRoot.childrenNames();
		for (int i = 0; i < names.length; i++)
			try {
				ids.add(new Long(names[i]));
			} catch (Exception ex) {
				DicrExceptionHandler.handleException(ex, this);
			}
		return ids;
	}

	/*******************************************************************************************************************
	 * Возвращает заданное соединение
	 * 
	 * @param id
	 * @return .
	 * @throws BackingStoreException
	 ******************************************************************************************************************/
	public synchronized ConnectionElement getConnection(long id) throws BackingStoreException {
		String nodename = Long.toString(id);
		if (!this.connRoot.nodeExists(nodename)) return null;
		Preferences pref = this.connRoot.node(nodename);
		return ConnMisc.getConnectionElement(pref);
	}

	/*******************************************************************************************************************
	 * Возвращает все соединения
	 * 
	 * @return .
	 * @throws BackingStoreException
	 ******************************************************************************************************************/
	@SuppressWarnings( { "boxing" })
	public synchronized Hashtable<Long, ConnectionElement> getConnections() throws BackingStoreException {
		Hashtable<Long, ConnectionElement> connections = new Hashtable<Long, ConnectionElement>();
		String[] names = this.connRoot.childrenNames();
		for (int i = 0; i < names.length; i++) {
			Long connNameId = new Long(names[i]);
			connections.put(connNameId, getConnection(connNameId));
		}
		return connections;
	}

	/*******************************************************************************************************************
	 * возвращает ID соединения по умолчанию
	 * 
	 * @return .
	 ******************************************************************************************************************/
	public synchronized long getDefaultConnectionID() {
		return checkDefaultConnection();
	}

	/*******************************************************************************************************************
	 * Возвращает элемент соединения по умолчанию
	 * 
	 * @return .
	 * @throws BackingStoreException
	 ******************************************************************************************************************/
	public synchronized ConnectionElement getDefaultConnection() throws BackingStoreException {
		return getConnection(getDefaultConnectionID());
	}

	/*******************************************************************************************************************
	 * Устанавливает ID соединения по умолчанию
	 * 
	 * @param id
	 * @throws Exception
	 ******************************************************************************************************************/
	public synchronized void setDefaultConnectionID(long id) throws Exception {
		long defid = getDefaultConnectionID();
		if (defid == id) return;
		String defname = Long.toString(id);
		if (!this.connRoot.nodeExists(defname)) throw new Exception("Соединение " + defname + " не существует");
		this.connRoot.put("Default", defname);
	}

	/*******************************************************************************************************************
	 * Добавляет новое соединение. Возвращает ID.
	 * 
	 * @param cel
	 ******************************************************************************************************************/
	public synchronized void setConnection(ConnectionElement cel) {
		if (cel == null) throw new IllegalArgumentException("cel is null");
		Preferences pref = this.connRoot.node(Long.toString(cel.getID()));
		ConnMisc.setPreferences(pref, cel);
		checkDefaultConnection();
	}

	/*******************************************************************************************************************
	 * Устанавливает соединение по умолчанию
	 * 
	 * @param cel
	 * @throws Exception
	 ******************************************************************************************************************/
	public synchronized void setDefaultConnection(ConnectionElement cel) throws Exception {
		setConnection(cel);
		setDefaultConnectionID(cel.getID());
	}

	/*******************************************************************************************************************
	 * Удаляет соединение
	 * 
	 * @param id
	 * @throws BackingStoreException
	 ******************************************************************************************************************/
	public synchronized void removeConnection(long id) throws BackingStoreException {
		String nodename = Long.toString(id);
		if (!this.connRoot.nodeExists(nodename)) return;
		Preferences pref = this.connRoot.node(nodename);
		pref.removeNode();
		checkDefaultConnection();
	}

	/*******************************************************************************************************************
	 * Добавляет слушателя
	 * 
	 * @param l
	 ******************************************************************************************************************/
	@Override
    public void addProgramListener(ProgramListener l) {
		this.listeners.addProgramListener(l);
	}

	/*******************************************************************************************************************
	 * Удаляет слушателя
	 * 
	 * @param l
	 ******************************************************************************************************************/
	@Override
    public void removeProgramListener(ProgramListener l) {
		this.listeners.removeProgramListener(l);
	}

	/*******************************************************************************************************************
	 * ######################################################################### Слушатель свойств
	 * #########################################################################
	 ******************************************************************************************************************/
	class PreferencesListener implements PreferenceChangeListener, NodeChangeListener {
		/***************************************************************************************************************
		 * Изменение свойств узла
		 * 
		 * @param e
		 **************************************************************************************************************/
		@Override
        public void preferenceChange(PreferenceChangeEvent e) {
			try {
				Preferences pref = e.getNode();
				if (pref == ConnectionsFactory.this.connRoot) {
					if (e.getKey().equals("Default")) ConnectionsFactory.this.listeners.fireProgramEvent(new ProgramEvent(
					        this, EVENT_DEFAULT_CHANGED, e.getNewValue() != null ? new Long(e.getNewValue()) : null));
				} else {
					while (pref != null && !ConnectionsFactory.this.connRoot.nodeExists(pref.name()))
						pref = pref.parent();
					if (pref != null) ConnectionsFactory.this.listeners.fireProgramEvent(new ProgramEvent(this,
					        EVENT_CONNECTION_CHANGED, new Long(pref.name())));
				}
			} catch (BackingStoreException ex) {
				DicrExceptionHandler.handleException(ex, this);
			} catch (IllegalStateException ex) {
				// NOP
			}
		}

		/***************************************************************************************************************
		 * Добавление соединения
		 * 
		 * @param e
		 **************************************************************************************************************/
		@Override
        public void childAdded(NodeChangeEvent e) {
			try {
				Preferences pref = e.getParent();
				if (pref == ConnectionsFactory.this.connRoot) ConnectionsFactory.this.listeners.fireProgramEvent(new ProgramEvent(
				        this, EVENT_CONNECTION_ADDED, new Long(e.getChild().name())));
				pref = e.getChild();
				addPreferencesListeners(pref);
			} catch (Exception ex) {
				DicrExceptionHandler.handleException(ex, this);
			}
		}

		/***************************************************************************************************************
		 * Удаление соединения
		 * 
		 * @param e
		 **************************************************************************************************************/
		@Override
        public void childRemoved(NodeChangeEvent e) {
			try {
				Preferences pref = e.getParent();
				if (pref == ConnectionsFactory.this.connRoot) ConnectionsFactory.this.listeners.fireProgramEvent(new ProgramEvent(
				        this, EVENT_CONNECTION_REMOVED, new Long(e.getChild().name())));
				pref = e.getChild();
				removePreferencesListener(pref);
			} catch (Exception ex) {
				DicrExceptionHandler.handleException(ex, this);
			}
		}
	}
}
